/*
 * printk.c
 *
 *	Description:
 *		Provides printk function for formatted printing of messages on STD out.
 *	Any driver which wants to service as character printer for printk has to
 *	register its put char function. Then printk uses this function to print
 *	future messages. Format specifiers are processed to print messages in
 *	formatted manner. Many data-types are supported.
 *	Example:
 *		Signed/unsigned integer
 *		Hexadecimal
 *		Signed/unsigned long integer
 *		String
 *
 *	printk() routine is the Primary routine for formatted printing of message
 *	in the kernel.
 *
 *
 *  Created on: Nov 30, 2013
 *      Author: Nishanth
 */

#include <stdint.h>
#include <string.h>
#include <stdarg.h>
#include <sys/types.h>

static void (*_putc)(char c);

/**********************************************************************
 * Description:
 * 		To register a function to print a character. This function
 * will be used by printk() for printing formatted messages.
 *
 * Input:
 * 		Function pointer, a void returning function with a char input
 *
 * 	Output:
 * 		None.
 *********************************************************************/
void registerPutcFunc(void (*pFunc)(char c))
{
	_putc = pFunc;
}

//Forward declarations
static int print_int32(int val);
static int print_hex32(uint32_t val, int x0);
static int print_uint32(uint32_t val);

static int print_int64(int64_t val64);
static int print_hex64(uint64_t val64);
static int print_uint64(uint64_t val64);

static int print_string(const char*);


/**********************************************************************
 * Description:
 * 		Prints Formatted strings for STD out. Format specifiers
 * provide what type of data to be printed and the data value is taken
 * from the argument list.
 *
 * Input:
 * 		One or more Formatted string messages to be printed allong with
 * other data values to be printed.
 *
 * Output:
 * 		Number of characters actually printed. -1 if failure.
 *********************************************************************/
int printk(char *fmt, ...)
{
	va_list arg;
	int ret = 0;
	uint32_t val;
	uint64_t val64;

	//Check if valid putc() is registered.
	if (_putc == NULL)
		return -1;

	// Variable argument processing
	va_start(arg, fmt);

	// Till the end of string
	while(*fmt != '\0')
	{
		// Format the data to be printed
		if(*fmt == '%')
		{
			fmt++;
			switch(*fmt)
			{
			case 'd':	// Print the integer
				val = va_arg(arg,int);
				ret += print_int32((int)val);
				break;
			case 'x':	// Print Hex
				val = va_arg(arg, uint32_t);
				ret += print_hex32(val, 1);
				break;
			case 'c':	// Print the char
				val = va_arg(arg, int);
				_putc((char)val);
				ret++;
				break;
			case 'u':	// Print unsigned int
				val = va_arg(arg, uint32_t);
				ret += print_uint32(val);
				break;
			case 's':	// Print the string
				val = va_arg(arg, uint32_t);
				ret += print_string((char*)val);
				break;
			case 'l':	// Long data
				switch(*(++fmt))
				{
				case 'u':
					val64 = va_arg(arg, uint64_t);
					ret += print_uint64(val64);
					break;
				case 'd':
					val64 = va_arg(arg, int64_t);
					ret += print_int64(val64);
					break;
				case 'x':
					val64 = va_arg(arg, uint64_t);
					ret += print_hex64(val64);
					break;
				default:
					val64 = va_arg(arg, int64_t);
					ret += print_int64(val64);
					break;
				}
				break;
			default:
				_putc(*fmt);
				ret++;
				break;
			}
		}
		else
		{
			// If no format print the character and go to next char
			_putc(*fmt);
			ret++;
		}

		fmt++;
	}
	va_end(arg);

	return 0;
}

/*	Print an int32 number */
static int print_int32(int val)
{
	char _str[11];		// Stores the processed chars, in reverse
	int rem;
	int temp = val;		// Temporary value
	int i = 0;
	int ret = 0;		// return value, number of chars written

	// if Zero, do it quick and get out
	if(val == 0)
	{
		_putc(0x30);
		return 1;
	}

	// negetive number, print a '-' sign first
	if(temp < 0)
	{
		temp *= -1;
		ret++;
		_putc('-');
	}

	while(temp && i < sizeof(_str))
	{
		rem = temp % 10;					// Reminder
		_str[i++] = (char)(rem + 0x30);		// Store corresponding ASCII
		temp = temp/10;
	}
	_str[i] = '\0';							// End the string

	// Print the string in reverse order
	for(i = strlen(_str); i >= 0; i--)
	{
		ret++;
		_putc(_str[i]);
	}

	return ret;
}

// Print Long data
static int print_int64(int64_t val64)
{
	uint32_t val_lo;
	int val_hi;
	int ret = 0;

	val_hi = (int)(val64 >> 32);
	if(val_hi)
		ret += print_int32(val_hi);

	val_lo = (uint32_t)val64;
	ret += print_int32(val_lo);

	return ret;
}

const char _hex_tbl[16] =  { '0', '1', '2', \
		'3', '4', '5', '6', '7', '8', \
		'9', 'A', 'B', 'C', 'D', 'E', \
		'F'};

// Print hexadecimal numerals
static int print_hex32(uint32_t val, int x0)
{
	int rsh = 28;
	int i = 0;
	int x;

	// if printing 0x at the beginning s requested by the caller
	if(x0)
	{
		_putc('0');
		_putc('x');
	}

	while( i < 8)
	{
		x = val >> rsh;			// Find the next-most significant byte
		x = x & 0x0000000F;		// Isolate the byte value
		_putc(_hex_tbl[x]);		// Print the hex char from look-up table

		rsh = rsh - 4;			// shift number decriments
		i++;
	}

	if(x0)
		return 10;
	else
		return 8;
}

// Print long number in hex
static int print_hex64(uint64_t val64)
{
	uint32_t val;
	int ret = 0;

	val = (uint32_t)(val64 >> 32);
	ret += print_hex32(val, 1);

	val = (uint32_t)val64;
	ret += print_hex32(val, 0);

	return ret;
}

// Print unsigned interger
static int print_uint32(uint32_t val)
{
	char _str[11];
	uint32_t rem;
	uint32_t temp = val;
	int i = 0;
	int ret = 0;

	if(val == 0)
	{
		_putc(0x30);
		return 1;
	}

	while(temp && i < sizeof(_str))
	{
		rem = temp % 10;
		_str[i++] = (char)(rem + 0x30);
		temp = temp/10;
	}
	_str[i] = '\0';

	for(i = strlen(_str); i >= 0; i--)
	{
		ret++;
		_putc(_str[i]);
	}

	return ret;
}

static int print_uint64(uint64_t val64)
{
	uint32_t val;
	int ret = 0;

	val = (uint32_t)(val64 >> 32);
	if(val)
		ret += print_uint32(val);

	val = (uint32_t)val64;
	ret += print_uint32(val);

	return ret;
}

// Print the strings
static int print_string(const char* str)
{
	int ret = 0;
	for(int i=0; str[i] != '\0'; i++)
	{
		ret++;
		_putc(str[i]);
	}

	return ret;
}

